package cmd

import (
	"fmt"
	"net"
	"sync"
	"time"

	"github.com/donnie4w/go-logger/logger"
)

type DceHandler func(*Conn, *ProtoDce) error

type CmdSvr struct {
	listener net.Listener
	laddr    string

	exitChan  chan struct{}
	waitGroup *sync.WaitGroup

	dceHandler DceHandler

	conns *sync.Map
}

func NewCmdSvr(laddr string, dceHandler DceHandler) *CmdSvr {
	svr := &CmdSvr{
		laddr:      laddr,
		exitChan:   make(chan struct{}),
		waitGroup:  &sync.WaitGroup{},
		dceHandler: dceHandler,
		conns:      &sync.Map{},
	}
	return svr
}

func (svr *CmdSvr) Start() error {
	var e error
	svr.listener, e = net.Listen("tcp", svr.laddr)
	if e != nil {
		return e
	}
	logger.Info(fmt.Sprintf("CmdSvr start, listen on %s", svr.laddr))

	svr.waitGroup.Add(1)
	defer func() {
		svr.listener.Close()
		svr.waitGroup.Done()
	}()

	var tempDelay time.Duration
	tempDelay = 0

	for {
		select {
		case <-svr.exitChan:
			return nil
		default:
		}

		rw, e := svr.listener.Accept()
		if e != nil {
			if ne, ok := e.(net.Error); ok && ne.Temporary() {
				if tempDelay == 0 {
					tempDelay = 5 * time.Millisecond
				} else {
					tempDelay *= 2
				}
				if max := 1 * time.Second; tempDelay > max {
					tempDelay = max
				}
				logger.Error(fmt.Sprintf("Accept error: %v; retrying in %v", e, tempDelay))
				time.Sleep(tempDelay)
				continue
			}
			return e
		}
		tempDelay = 0

		svr.waitGroup.Add(1)
		go func() {
			c := newConn(svr, rw)
			addr := c.Addr()
			svr.conns.Store(addr, c)
			c.serve()
			svr.conns.Delete(addr)
			svr.waitGroup.Done()
		}()
	}
}

func (svr *CmdSvr) Stop() {
	close(svr.exitChan)
	svr.waitGroup.Wait()
	logger.Info(fmt.Sprintf("CmdSvr stop"))
}

func (svr *CmdSvr) Conns() []string {
	cs := []string{}
	svr.conns.Range(func(key, value interface{}) bool {
		cs = append(cs, value.(*Conn).Addr())
		return true
	})
	return cs
}
